import base64
import bisect
import os
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime, timedelta

from apscheduler.triggers.date import DateTrigger
from flask import Blueprint, request

from .config import config
from .databus import capture_datetimes
from .db import db
from .model.base_model import BaseModel
from .model.program import Program, ProgramType
from .model.tag import Tag, TagType
from .task import jobs, notify, scheduler
from .util import SerializableDBType, generate_hex_str

api_bp = Blueprint('api', __name__, url_prefix='/api')

thread_pool = ThreadPoolExecutor()


def model_list_to_serealized(l: list[BaseModel]) -> list[dict[str, SerializableDBType]]:
    return [e.to_serealized_dict() for e in l]


def get_date_range(date_str: str) -> tuple[datetime, datetime]:
    [year, month, day] = date_str.split('_')
    dt_from = datetime(int(year), int(month), int(day))
    dt_to = datetime(int(year), int(month), int(day), 23, 59, 59, 999999)
    return dt_from, dt_to


def timestamp_to_datetime(timestamp: int) -> datetime:
    # 所有的时间都是精确到秒
    return datetime.fromtimestamp(int(timestamp / 1000))

# API


@api_bp.route('/test')
def test() -> str:
    arg = request.args.get('arg')
    return f'hello, {arg}'


@api_bp.post('/test_post')
def test_post() -> str:
    arg = request.json.get('arg', None)
    arg2 = request.json.get('arg2', None)
    return f'hello, {arg}, {arg2}'


@api_bp.route('/get_program_line')
def get_program_line() -> list[Program]:
    date_str = request.args.get('date_str', default=None, type=str)
    if date_str:
        dt_from, dt_to = get_date_range(date_str)
        l: list[Program] = db.session.query(Program).filter(Program.end_time > dt_from).filter(Program.begin_time < dt_to).all()
    else:
        l: list[Program] = db.session.query(Program).all()
    return model_list_to_serealized(l)


@api_bp.route('/get_program_types')
def get_program_types() -> list[ProgramType]:
    l: list[ProgramType] = db.session.query(ProgramType).all()
    return model_list_to_serealized(l)


@api_bp.route('/get_tag_line')
def get_tag_line() -> list[Tag]:
    date_str = request.args.get('date_str', default=None, type=str)
    if date_str:
        dt_from, dt_to = get_date_range(date_str)
        l: list[Tag] = db.session.query(Tag).filter(Tag.end_time > dt_from).filter(Tag.begin_time < dt_to).all()
    else:
        l: list[Tag] = db.session.query(Tag).all()
    return model_list_to_serealized(l)


@api_bp.route('/get_tag_types')
def get_tag_types() -> list[TagType]:
    l: list[TagType] = db.session.query(TagType).all()
    return model_list_to_serealized(l)


@api_bp.post('/update_tag_type')
def update_tag_type() -> bool:
    id_: int = request.json.get('id')
    name: str = request.json.get('name', None)
    color: str = request.json.get('color', None)
    try:
        tag_type = db.session.query(TagType).filter_by(id=id_).first()
        if name:
            tag_type.name = name
        if color:
            tag_type.color = color
        db.session.commit()
    except Exception as e:
        return False
    return True


@api_bp.post('/add_tag')
def add_tag() -> bool:
    name: str = request.json.get('name')
    begin_timestamp: int = request.json.get('begin_timestamp')
    end_timestamp: int = request.json.get('end_timestamp')
    note: str = request.json.get('note', '')

    try:
        # ，左闭右开
        tag_type = db.session.query(TagType).filter_by(name=name).first()
        if tag_type is None:
            TagType(name, generate_hex_str(6)).save()
        Tag(
            name,
            begin_time=timestamp_to_datetime(begin_timestamp),
            end_time=timestamp_to_datetime(end_timestamp),
            note=note,
        ).save()
    except Exception as e:
        import traceback
        traceback.print_exc()
        print(e)
        return False
    return True


@api_bp.route('/update_tag')
def update_tag() -> bool:
    id_: int = request.json.get('id')
    name: str | None = request.json.get('name', None)
    begin_timestamp: int | None = request.json.get('begin_timestamp', None)
    end_timestamp: int | None = request.json.get('end_timestamp', None)
    note: str|None = request.json.get('note', None)
    try:
        tag = db.session.query(Tag).filter_by(id=id_).first()
        if name:
            tag.name = name
        if begin_timestamp:
            tag.begin_time = timestamp_to_datetime(begin_timestamp)
        if end_timestamp:
            tag.end_time = timestamp_to_datetime(end_timestamp)
        if note:
            tag.note = note
        db.session.commit()
    except Exception as e:
        return False
    return True


@api_bp.route('/read_file_byte_list/<str:path>')
def read_file_byte_list(path: str) -> list[int]:
    with open(path, 'rb') as f:
        bs: bytes = f.read()
        return list(bs)

@api_bp.route('/get_last_capture_path')
def get_last_capture_path() -> str:
    timestamp = request.args.get('timestamp', type=int)
    dt = timestamp_to_datetime(timestamp)
    # 使用 bisect_left() 找到 dt 在 dts 中的插入位置
    index = bisect.bisect_right(capture_datetimes, dt)
    # 如果 index 为 0，说明 dt 比 capture_datetimes 中的所有元素都小，没有在 dt 之前的元素
    if index == 0:
        print("No datetime found before dt.")
        return ''
    else:
        # 返回插入位置之前的一个元素
        last_dt_before = capture_datetimes[index - 1]
        print(timestamp, last_dt_before)
    path = os.path.join(config.capture_path, last_dt_before.strftime('%Y_%m_%d_%H_%M_%S.png'))
    return path
    # return self.read_file_byte_list(path)

# def read_base64_file(path: str) -> str:
#     print('read_base64_file', path)
#     future = thread_pool.submit(self._read_base64_file, path)
#     print('finish', path)
#     return future.result()


# def write_file(path: str, content: str) -> None:
#     with open(path, 'wb') as f:
#         f.write(content)


# def delete_file(path: str) -> None:
#     os.remove(path)


@api_bp.route('/set_clock')
def set_clock() -> str:
    second = request.args.get('second', type=int)
    trigger_time = datetime.now() + timedelta(seconds=second)
    trigger = DateTrigger(run_date=trigger_time)
    job = scheduler.add_job(notify, trigger=trigger)
    # jobs.append(job)
    return job.id

# @api_bp.route('/cancel_clock')
# def cancel_clock(job_id: str) -> None:
#     scheduler.remove_job(job_id)
